package com.example.jpa.crud.model;

import com.example.jpa.crud.annotation.Crud;
import com.example.jpa.crud.annotation.CrudField;
import com.example.jpa.crud.annotation.ViewTemplate;
import com.example.jpa.crud.exception.ResourceModelNotFoundException;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.stereotype.Component;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class ResourceModelSupportForSpringBoot3 extends ResourceModelSupport {

    private static final Logger log = LoggerFactory.getLogger(ResourceModelSupportForSpringBoot3.class);

    private final Map<String, ResourceModel> resourceModels = new ConcurrentHashMap<>();

    private JsonViewTemplate jsonViewTemplate = new DefaultJsonViewTemplate();

    @Override
    public ResourceModel getResourceModelByName(String name) {
        final ResourceModel resourceModel = this.resourceModels.get(name);
        if (resourceModel == null) {
            throw new ResourceModelNotFoundException("resource '" + name + "' not found!");
        }
        return resourceModel;
    }

    @Override
    public JsonViewTemplate getResponseTemplate() {
        return jsonViewTemplate;
    }

    private ResourceModel createResourceModel(Class<?> entityClass) {
        final Entity entity = entityClass.getDeclaredAnnotation(Entity.class);
        if (entity == null) {
            log.error("class '{}' without Annotation '@Entity'!", entityClass.getName());
            return null;
        }
        final Table table = entityClass.getDeclaredAnnotation(Table.class);
        if (table == null) {
            log.error("class '{}' without Annotation '@Table'!", entityClass.getName());
            return null;
        }
        final Crud crud = entityClass.getDeclaredAnnotation(Crud.class);
        final String name = crud.name();
        final String value = crud.value();
        String resourceName;
        if ("".equals(name) && "".equals(value)) {
            resourceName = entityClass.getSimpleName();
        } else if (!"".equals(name)) {
            resourceName = name;
        } else {
            resourceName = value;
        }
        String idName = "";
        Class<?> idClass = null;
        final Field[] declaredFields = entityClass.getDeclaredFields();
        final Map<String, Field> fields = new HashMap<>();
        final Map<String, DisplayStrategy> fieldToDisplayStrategy = new HashMap<>();
        for (Field field : declaredFields) {
            field.setAccessible(true);
            String fieldName = field.getName();
            fields.put(fieldName, field);
            Id id = field.getDeclaredAnnotation(Id.class);
            if (idClass == null && id != null) {
                idName = fieldName;
                idClass = field.getType();
            }
            CrudField crudField = field.getDeclaredAnnotation(CrudField.class);
            if (crudField == null) {
                fieldToDisplayStrategy.put(fieldName, DisplayStrategy.DEFAULT);
            } else {
                fieldToDisplayStrategy.put(fieldName, crudField.display());
            }
        }
        ResourceModel model = new ResourceModel();
        model.setName(resourceName);
        model.setTableName(table.name());
        model.setIdName(idName);
        model.setEntityClass(entityClass);
        model.setIdClass(idClass);
        model.setAutoGenerateId(crud.autoGenerateId());
        model.setFields(fields);
        model.registerDisplayStrategy(fieldToDisplayStrategy);
        return model;
    }

    @Override
    protected void registerResourceModel(String packageName) {
        final ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        scanner.addIncludeFilter(new AnnotationTypeFilter(Crud.class));
        final Set<BeanDefinition> beanDefinitions = scanner.findCandidateComponents(packageName);
        for (BeanDefinition beanDefinition : beanDefinitions) {
            try {
                final Class<?> entityClass = Class.forName(beanDefinition.getBeanClassName());
                final ResourceModel resourceModel = createResourceModel(entityClass);
                if (resourceModel != null) {
                    this.resourceModels.put(resourceModel.getName(), resourceModel);
                    log.info("ResourceModel '{}' has been registered successfully", resourceModel.getName());
                }
            } catch (ClassNotFoundException e) {
                log.error("registerResourceModel", e);
            }
        }
    }

    @Override
    protected void registerJsonViewTemplate(String packageName) {
        final ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        scanner.addIncludeFilter(new AnnotationTypeFilter(ViewTemplate.class));
        final Set<BeanDefinition> beanDefinitions = scanner.findCandidateComponents(packageName);
        for (BeanDefinition beanDefinition : beanDefinitions) {
            try {
                final Class<?> clazz = Class.forName(beanDefinition.getBeanClassName());
                if (JsonViewTemplate.class.isAssignableFrom(clazz)) {
                    final Constructor<?> constructor = clazz.getDeclaredConstructor();
                    constructor.setAccessible(true);
                    this.jsonViewTemplate = (JsonViewTemplate) constructor.newInstance();
                    log.info("JsonViewTemplate '{}' has been registered successfully", clazz.getName());
                }
            } catch (Exception e) {
                log.error("registerJsonViewTemplate", e);
            }
        }
    }

}
